#include<iostream>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int vis[100];
int mx[100][100];//初始矩阵
int mp[100][100];//临时地图
int ans[100][100];//最终翻转次数
int temp[100][100];//临时翻转次数
int m,n;
int res = INF;

//模拟翻转
void flip(int i,int j){
	mp[i][j] = !mp[i][j];
	mp[i - 1][j] = !mp[i - 1][j];
	mp[i][j - 1] = !mp[i][j - 1];
	mp[i + 1][j] = !mp[i + 1][j];
	mp[i][j + 1] = !mp[i][j + 1];
}
//对当前vis序列的情况开始模拟
void solve(){
	//初始化临时翻转地图
	memset(temp,0,sizeof(temp));
	//先读入初始地图
	for(int i = 1;i <= m;i++){
		for(int j = 1;j <= n;j++){
			mp[i][j] = mx[i][j];
		}
	}
	//然后读入第一行的翻转，更新此时地图
	for(int i = 1;i <= n;i++){
		if(vis[i]){
			//翻转第一行第i个
			flip(1,i);
			//记录翻转版本的地图
			temp[1][i] = 1;
		}
	}
	//接下来从第二行开始模拟，原则是：因为第一行的翻转情况已经固定，所以满足（上一行只能由下一行的翻转决定）
	for(int i = 2;i <= m;i++){
		for(int j = 1;j <= n;j++){
			//如果上一行第j格需要翻转，就翻转
			if(mp[i - 1][j]){
				flip(i,j);
				//翻转版本的地图别忘了记录
				temp[i][j] = 1;
			}
		}
	}
	//接下来看看最后一行满不满足就行
	for(int i = 1;i <= n;i++){
		//不满足
		if(mp[m][i]){
			return ;
		}
	}
	int cnt = 0;
	for(int i = 1;i <= m;i++){
		for(int j = 1;j <= n;j++){
			if(temp[i][j]) cnt++;
		}
	}
	//找到更小的，更新最终翻转版本地图
	if(cnt < res){
		for(int i = 1;i <= m;i++){
			for(int j = 1;j <= n;j++){
				ans[i][j] = temp[i][j];
			}
		}
		res = cnt;
	}
}
//dfs第一行的每一格的情况，从第一格到第m格，最后会把第一行的所有情况都出现一遍
void dfs(int x){
	//第一行结束
	if(x == n + 1){
		solve();
		return;
	}
	//记录第一行每个格子是0还是1
	vis[x] = 0;
	dfs(x + 1);
	vis[x] = 1;
	dfs(x + 1);
}
int main(){

	cin >> m >> n;
	for(int i = 1;i <= m;i++){
		for(int j = 1;j <= n;j++){
			cin >> mx[i][j];
		}
	}
	dfs(1);
	if(res == INF){
		cout << "IMPOSSIBLE" << "\n";
	}else{
		for(int i = 1;i <= m;i++){
			for(int j = 1;j <= n;j++){
				cout << ans[i][j] << " ";
			}
			cout << "\n";
		}
	}
}